//
//  NSObject+JRSimpleKVO.m
//  SimpleKVO
//
//  Created by 丁嘉睿 on 2017/6/1.
//  Copyright © 2017年 achen. All rights reserved.
//

#import "NSObject+JRSimpleKVO.h"
#import <objc/runtime.h>

@interface JRKVOBlockTarget : NSObject

@property (nonatomic, copy) void (^block)(NSDictionary<NSKeyValueChangeKey,id> *change, NSString *keyPath);

- (id)initWithBlock:(void (^)(NSDictionary<NSKeyValueChangeKey,id> *change, NSString *keyPath))block;

@end

@implementation JRKVOBlockTarget

- (id)initWithBlock:(void (^)(NSDictionary<NSKeyValueChangeKey,id> *change, NSString *keyPath))block{
    self = [super init];
    if (self) self.block = block;

    return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    /** 由于iOS11，方法属于异步的了。所以得主线程回去 */
    if ([NSThread isMainThread]) {
        if (self.block != nil) self.block(change, keyPath);
    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            if (self.block != nil) self.block(change, keyPath);
        });
    }
}

@end

@implementation NSObject (JRSimpleKVO) 

void import_NSObject_JRSimpleKVO_Compression (void) { }


#pragma mark - Public Methods

- (void)addKVOForPath:(NSString *)path withChangeBlock:(void (^)(NSDictionary<NSKeyValueChangeKey,id> *))block{
    if (path.length == 0) return;
    void(^weakBlock)(NSDictionary<NSKeyValueChangeKey,id> *dict) = block;
    [self addKVOForPaths:@[path] withChangeBlock:^(NSDictionary<NSKeyValueChangeKey,id> *change, NSString *keyPath) {
        if (weakBlock) weakBlock(change);
    }];
}

- (void)addKVOForPath:(NSString *)path options:(NSKeyValueObservingOptions)options withChangeBlock:(void (^)(NSDictionary<NSKeyValueChangeKey,id> *))block{
    void(^weakBlock)(NSDictionary<NSKeyValueChangeKey,id> *dict) = block;
    [self addKVOForPaths:@[path] options:options withChangeBlock:^(NSDictionary<NSKeyValueChangeKey,id> *change, NSString *keyPath) {
        if (weakBlock) weakBlock(change);
    }];
}

- (void)addKVOForPaths:(NSArray<NSString *> *)paths withChangeBlock:(void (^)(NSDictionary<NSKeyValueChangeKey,id> *, NSString *))block{
    [self addKVOForPaths:paths options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld withChangeBlock:block];
}

- (void)addKVOForPaths:(NSArray<NSString *> *)paths options:(NSKeyValueObservingOptions)options withChangeBlock:(void (^)(NSDictionary<NSKeyValueChangeKey,id> *, NSString *))block{
    if (paths.count == 0 || block == nil || ![[paths firstObject] isKindOfClass:[NSString class]]) return;
    JRKVOBlockTarget *target = [[JRKVOBlockTarget alloc] initWithBlock:block];
    NSMutableDictionary *dic = [self jr_createKVOBlocksLazyLoad];
    for (NSString *keyPath in paths) {
        NSMutableArray *blockTargetsForPath = dic[keyPath];
        if (!blockTargetsForPath)
        {
            blockTargetsForPath = @[].mutableCopy;
            dic[keyPath] = blockTargetsForPath;
        }
        if (![blockTargetsForPath containsObject:target]) [blockTargetsForPath addObject:target];
        [self addObserver:target forKeyPath:keyPath options:options context:NULL];
    }
}

- (void)removeKVOForPath:(NSString *)path {
    if ([path length] > 0)
    {
        NSMutableDictionary *dic = [self jr_getAssociatedObject];
        if (dic == nil) return;
        id obj = [dic objectForKey:path];
        NSMutableArray *arr;
        if (obj && [obj isKindOfClass:[NSMutableArray class]]) {
            arr = (NSMutableArray *)obj;
        } else {
            return;
        }
        [arr enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
            [self removeObserver:obj forKeyPath:path];
        }];
        [dic removeObjectForKey:path];
    }
}

- (void)removeKVOForPaths:(NSArray<NSString *> *)paths{
    for (NSString *keyPath in paths) {
        [self removeKVOForPath:keyPath];
    }
}

- (void)removeAllKVOs
{
    NSMutableDictionary *dic = [self jr_getAssociatedObject];
    if (dic == nil) return;
    [dic enumerateKeysAndObjectsUsingBlock: ^(NSString *key, NSArray *arr, BOOL *stop) {
        [arr enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
            [self removeObserver:obj forKeyPath:key];
        }];
    }];
    [dic removeAllObjects];
    objc_setAssociatedObject(self, @selector(jr_getAssociatedObject), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

#pragma mark - Private Methods
#pragma mark runtime
- (NSMutableDictionary *)jr_createKVOBlocksLazyLoad
{
    NSMutableDictionary *targets = [self jr_getAssociatedObject];
    if (!targets)
    {
        targets = [NSMutableDictionary new];
        objc_setAssociatedObject(self, @selector(jr_getAssociatedObject), targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return targets;
}

- (NSMutableDictionary *)jr_getAssociatedObject
{
    return objc_getAssociatedObject(self, _cmd);
}

#pragma mark 替换方法
//+(void)load{
//    static dispatch_once_t onceToken;
//    dispatch_once(&onceToken, ^{
//        NSString *selString = @"dealloc";
//        Method originalDealloc = class_getInstanceMethod(self, NSSelectorFromString(selString));
//
//        NSString *kvoSelString = [@"JRSimpleKVO_" stringByAppendingString:selString];
//        Method kvoDealloc = class_getInstanceMethod(self, NSSelectorFromString(kvoSelString));
//        //首先动态添加方法，实现是被交换的方法，返回值表示添加成功还是失败
//        BOOL isYES = class_addMethod(self, NSSelectorFromString(selString), method_getImplementation(kvoDealloc), method_getTypeEncoding(kvoDealloc));
//        if (isYES) {
//            //如果成功，说明类中不存在这个方法的实现
//            //将被交换方法的实现替换到这个并不存在的实现
//            class_replaceMethod(self, NSSelectorFromString(kvoSelString), method_getImplementation(originalDealloc), method_getTypeEncoding(originalDealloc));
//        } else {
//            //否则，交换两个方法的实现
//            method_exchangeImplementations(originalDealloc, kvoDealloc);
//        }
//    });
//    //在替换方法后，记得还要调用自己。这里自己调用自己不会死循环，因为已经替换了。
//}

- (BOOL)isSimpleKVO
{
    NSMutableDictionary *dict = [self jr_getAssociatedObject];
    return dict.count > 0 ? YES : NO;
}

- (void)JRSimpleKVO_dealloc
{
    if ([self isSimpleKVO]) [self removeAllKVOs];
    [self JRSimpleKVO_dealloc];
}

@end
